package com.mintandleaf.webkits.core;

import com.google.inject.Binding;
import com.google.inject.Guice;
import com.google.inject.Injector;
import com.google.inject.Key;
import com.google.inject.Module;
import com.google.inject.Stage;
import com.google.inject.name.Named;
import com.mintandleaf.webkits.core.exception.ModulesNotFoundException;
import com.mintandleaf.webkits.core.view.WebKitsJspResolver;
import com.mintandleaf.webkits.util.WebKitsErrorMessage;
import java.io.IOException;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

/**
 * Core Dispatcher server
 * @author teguhh
 */
public class WebKitsDispatcher extends HttpServlet {

    /**
     * Don't forget to call super.init(config) if subclass override this method
     * @param config
     * @throws javax.servlet.ServletException
     */
    private Injector injector = null;

    private HashMap<String ,Key> urlMap=new HashMap<String, Key>();
    private WebKitsViewResolver viewResolver;
    @Override
    public void init(ServletConfig paramServletConfig) throws ServletException {
        String moduleBinder=paramServletConfig.getInitParameter("modulebinder");
        if(moduleBinder==null || moduleBinder.trim().equals(""))
            throw new ServletException("Cannot find init parameter in web.xml: <servlet>"
                    + "<servlet-name>?</servlet-name><servlet-class>"
                    + getClass().getName()
                    + "</servlet-class><init-param><param-name>modulebinder</param-name><param-value>"
                    + "put-your-config-module-full-class-name-here</param-value></init-param></servlet>");
        //initialize the injector
        injector = Guice.createInjector(Stage.PRODUCTION, getConfigModule(moduleBinder));
        //sorry still only supported JSP 
        this.viewResolver=new WebKitsJspResolver();
        this.urlMap=getUrlMap();
           
    }
   
    /** 
     * Processes requests for both HTTP <code>GET</code> and <code>POST</code> methods.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    protected void processRequest(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException,ModulesNotFoundException {
        
        String contextPath = request.getContextPath();
        try{
               
                HttpSession session=request.getSession();
                ServletContext context=session.getServletContext();
                WebKitsModuleContext.setWebKitsActionContext(request, response, session, context);
                 WebKitsWebModule module=getWebModule(request.getRequestURI().substring(contextPath.length()));
                WebKitsModelAndView mv= module.invoke(request, response);
                if(mv!=null)
                    render(mv,request,response);
        }catch(NullPointerException exc){
            throw  new ModulesNotFoundException(request.getRequestURI());
                   
        }catch(ServletException exc){
            return;
        }catch(Exception exc){
            return;
        }
    } 

    // <editor-fold defaultstate="collapsed" desc="HttpServlet methods. Click on the + sign on the left to edit the code.">
    /** 
     * Handles the HTTP <code>GET</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doGet(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        try{
            processRequest(request, response);

        }catch(Exception exc){
            //WebKitsErrorMessage.showError(response,exc);
            WebKitsErrorMessage.showError(exc);
        }
    } 

    /** 
     * Handles the HTTP <code>POST</code> method.
     * @param request servlet request
     * @param response servlet response
     * @throws ServletException if a servlet-specific error occurs
     * @throws IOException if an I/O error occurs
     */
    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response)
    throws ServletException, IOException {
        try{
            processRequest(request, response);
        }catch(Exception exc){
            
            //WebKitsErrorMessage.showError(response,exc);
            WebKitsErrorMessage.showError(exc);
        }
    }

    /** 
     * Returns a short description of the servlet.
     * @return a String containing servlet description
     */
    @Override
    public String getServletInfo() {
        return "Short description";
    }// </editor-fold>


    /**
     * Get a config module for IoC config. NOTE that all Action classes must
     * bind with annotation of @Named("url") with a valid url.
     *
     * @return A config module.
     */
    protected Module getConfigModule(String className) throws ServletException {
        Object obj = null;
        try {
            Class<?> clazz = Class.forName(className);
            obj = clazz.newInstance();
            if(!(obj instanceof WebKitsModuleBinder))
                throw new ServletException("Cannot instantiat for class \"" + className + "\"");
        }
        catch(InstantiationException e) {
            throw new ServletException("Cannot instantiat for class \"" + className + "\"", e);
        }
        catch(IllegalAccessException e) {
            throw new ServletException("Cannot instantiat for class \"" + className + "\"", e);
        }
        catch(ClassNotFoundException e) {
            throw new ServletException("Cannot find class \"" + className + "\"", e);
        }
        try {
            Module module = (Module)obj;
            /*
            if(module instanceof ServletContextAware) {
                ((ServletContextAware)module).setServletContext(context);
            }*/
            return module;
        }
        catch(ClassCastException e) {
            throw new ServletException("Cannot cast class \"" + className + "\" to \"" + Module.class.getName() + "\"", e);
        }
    }

    /**
     * Get url maping from module binder
     * @return HashMap object of urlmap
     */
    protected HashMap<String,Key> getUrlMap(){
        HashMap<String,Key> ret=new HashMap<String, Key>();
        Iterator<Key<?>> iterKey=findKeysByType(injector, WebKitsWebModule.class).iterator();
        while(iterKey.hasNext()){
            Key tempKey=iterKey.next();
            Named a=(Named) tempKey.getAnnotation();
            ret.put(a.value(), tempKey);
        }
        return ret;
    }

    /**
     * Get list of available key in injector
     * @return List of available Key object in the injector
     **/
    private static List<Key<?>> findKeysByType(Injector inj, Class<?> type) {
        Map<Key<?>, Binding<?>> map = inj.getBindings();
        List<Key<?>> keyList = new ArrayList<Key<?>>();
        for(Key<?> key : map.keySet()) {
            Type t = key.getTypeLiteral().getType();
            if(t instanceof Class<?>) {
                Class<?> clazz = (Class<?>) t;
                if(type==null || type.isAssignableFrom(clazz)) {
                    keyList.add(key);
                }
            }
        }
        return keyList;
    }

    /**
     * Get WebKitsWebModule object based the url mapping
     * @param paramUrlMap
     * @return WebKitsWebModule object
     * @throws java.lang.ClassCastException
     */
    protected WebKitsWebModule getWebModule(String paramUrlMap) {
        return (WebKitsWebModule)injector.getInstance(urlMap.get(paramUrlMap));
    }

    private void render(WebKitsModelAndView mv, HttpServletRequest reqest, HttpServletResponse response) throws ServletException, IOException {
        String view = mv.getView();
        if(view.startsWith("redirect:")) {
            String redirect = view.substring("redirect:".length());
            response.sendRedirect(redirect);
            return;
        }
        Map<String, Object> model = mv.getModel();
        if(viewResolver!=null)
            viewResolver.resolveView(view, model, reqest, response);
    }

    private WebKitsViewResolver findViewResolver(List<Key<?>> viewResolverKeys) {
        if(viewResolverKeys.size()==0) {
           return null;
        }
        if(viewResolverKeys.size()>1) {
            System.out.println("Found " + viewResolverKeys.size() + " ViewResolvers, only the first valid ViewResolver is available.");
        }
        for(Key<?> key : viewResolverKeys) {
            WebKitsViewResolver resolver = (WebKitsViewResolver) safeInstantiate(key);
            if(resolver!=null) {
                System.out.println("Found ViewResolver: " + resolver);
                return resolver;
            }
        }
        System.out.println("Cannot instantiate any ViewResolver. ViewResolver will be unavailable.");
        return null;
    }

    private <T> T safeInstantiate(Key<T> key) {
        try {
            return injector.getProvider(key).get();
        }
        catch(Exception e) {
            System.out.println("Cannot instantiate by key: " + key+e);
            return null;
        }
    }

}
